<?php

/**
 * 文件上传辅助类
 *
 * 辅助处理上传文件，可方便上传及上传文件管理
 *
 * @author    Garbin
 * @package    Library
 * @subpackage Ultity
 */


/**
 * 获取上传类快捷方式
 *
 * 相关配置项
 * <ol>
 *  <li>uploader.upload_root        ---     上传根目录</li>
 *  <li>uploader.checker            ---     上传文件检查器，该函数为回调函数，用于检查文件是否允许被上传</li>
 *  <li>uploader.checker_param      ---     检查参数，视不同检查器不同而内容可不同</li>
 * </ol>
 *
 * @author    Garbin
 * @return Object
 */
function __uploader()
{
    static $uploader = null;
    if ($uploader === null)
    {
        $config = array();
        app_config('uploader.upload_root') && $config['upload_root'] = app_config('uploader.upload_root');
        app_config('uploader.checker') && $config['checker'] = app_config('uploader.checker');
        app_config('uploader.checker_param') && $config['checker_param'] = app_config('uploader.checker_param');
        $uploader = new Uploader($config);
    }

    return $uploader;
}

/**
 * 文件上传器
 *
 * 辅助处理文件上传及管理
 *
 * @package Library
 * @subpackage Ultity
 */
class Uploader extends Object
{
    /**
     * 配置信息
     */
    protected $_config = array();

    public function __construct($config = array())
    {
        $this->config($config);
    }

    /**
     * 配置Uploader
     *
     * 可配置项
     * <ol>
     * <li>upload_root      ---     上传根目录</li>
     * <li>checker          ---     上传文件检查器</li>
     * <li>checker_param    ---     检查器参数</li>
     * </ol>
     *
     * @author    Garbin
     * @access public
     * @param array $config 配置项数组
     * @return void
     */
    public function config($config = array())
    {
        $this->_config = array_merge($this->_config, $config);
    }

    /**
     * 获取文件扩展名
     *
     * 根据指定文件名获取其扩展名
     *
     * @author    Garbin
     * @access public
     * @param string $file 文件名
     * @return string 返回其扩展名
     */
    public function getFileExt($file)
    {
        $tmp = pathinfo($file);

        return $tmp['extension'];
    }

    /**
     * 上传指定文件
     *
     * 上传指定文件至指定目录，并以指定文件名保存，成功返回文件元信息及文件保存路径(相对于upload_root)
     *
     * @author    Garbin
     * @access public
     * @param  array $source_file   上传文件信息，该数组结构与单个$_FILES数组一致
     * @param  string $save_dir     上传保存路径，相对于upload_root，路径不存在系统会尝试创建
     * @param  string $save_file_name 保存文件名，null为使用原始文件名，该文件名不包含扩展名
     * @param  string $ext          使用自定义扩展名
     * @return mixed 上传成功返回保存的相对upload_root的路径
     */
    public function upload($source_file, $save_dir, $save_file_name = null, $ext = null)
    {
        // 确保文件已经上传
        if (!is_uploaded_file($source_file['tmp_name']))
        {
            throw new Exception('No such uplode file');
        }
        $orig_ext = $this->getFileExt($source_file['name']);
        if ($this->_config['checker'] !== null)
        {
            $source_data = $source_file;
            $source_data['ext'] = $orig_ext;
            if (!call_user_func($this->_config['checker'], $source_data, $this->_config['checker_param']))
            {
                return false;
            }
        }

        // 确保上传路径可用
        $dir = $this->_config['upload_root'] . $save_dir;
        @smkdir($dir);

        // 获取保存的文件名
        $file_name = $save_file_name === null ? $source_file['name'] : $save_file_name;

        // 如果指定的使用自定义文件名，需要加上文件扩展名
        if ($save_file_name)
        {
            $ext = $ext === null ? $orig_ext : $ext;
            $file_name .= '.' . $ext;
        }

        // 将文件上传至指定路径
        $target_path = $dir . $file_name;
        if (!move_uploaded_file($source_file['tmp_name'], $target_path))
        {
            throw new Exception('Upload file failed');
        }

        return $save_dir . $file_name;
    }

    /**
     * 移除上传文件
     *
     * 移除指定上传文件，该路径相对于upload_root
     *
     * @author    Garbin
     * @access public
     * @param string $file_path 文件路径，相对于upload_root
     * @return bool 返回移除结果,true为移除成功，false为移除失败
     */
    public function remove($file_path)
    {
        $abs_path = $this->_config['upload_root'] . $file_path;

        return unlink($abs_path);
    }
}

/**
 * 默认检查器
 *
 * 默认上传文件检查器
 *
 * @author    Garbin
 * @access public
 * @param array $upload_info    由uploader回调传递过来的上传文件信息
 * @param array $param          检查器配置
 * @return bool     检查通过返回true，检查不通过返回false
 */
function default_checker($upload_info, $param = array())
{
    if (empty($param))
    {
        return true;
    }
    if ($param['ext'])
    {
        if (!in_array($upload_info['ext'], explode('|', $param['ext'])))
        {
            throw new Exception('File not allow upload');
        }
    }

    return true;
}
